package ar.edu.unicen.exa.isistan.archassis.diff;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import jdiff.APIComparator;
import jdiff.APIDiff;
import jdiff.ClassAPI;
import jdiff.ClassDiff;
import jdiff.ConstructorAPI;
import jdiff.FieldAPI;
import jdiff.MemberDiff;
import jdiff.MethodAPI;
import jdiff.Modifiers;
import jdiff.PackageAPI;
import jdiff.PackageDiff;
import jdiff.ParamAPI;
import ar.edu.unicen.exa.isistan.archassis.actions.ActionType;
import ar.edu.unicen.exa.isistan.archassis.actions.ClassAction;
import ar.edu.unicen.exa.isistan.archassis.actions.ConstructorAction;
import ar.edu.unicen.exa.isistan.archassis.actions.FieldAction;
import ar.edu.unicen.exa.isistan.archassis.actions.IAction;
import ar.edu.unicen.exa.isistan.archassis.actions.ImplementAction;
import ar.edu.unicen.exa.isistan.archassis.actions.MemberAction;
import ar.edu.unicen.exa.isistan.archassis.actions.MemberType;
import ar.edu.unicen.exa.isistan.archassis.actions.MethodAction;
import ar.edu.unicen.exa.isistan.archassis.actions.PackageAction;
import ar.edu.unicen.exa.isistan.archassis.actions.ParamAction;

/**
 * Clase utilitaria para leer un {@link APIComparator} y convertirlo en una
 * {@link List} de {@link IAction}.
 * 
 * @author sebastianperruolo@gmail.com
 *
 */
public final class JavaDiffAdapter {
	
	/**
	 * Creador privado porque es una clase utilitaria.
	 */
	private JavaDiffAdapter() {
		
	}

	/**
	 * Unico (espero) método público. Hace lo que dice el comentario de 
	 * esta clase.
	 * 
	 * @param apiComparator Comparación de JavaDiff.
	 * @return Lista de Acciones.
	 */
	public static List<IAction> process(final APIComparator apiComparator) {
		List<IAction> list = newList();
		
		System.out.println(
				APIDiff.oldAPIName_ // apiComparator.apiDiff.oldAPIName_ 
				+ " cambio por " 
				+ APIDiff.newAPIName_); // apiComparator.apiDiff.newAPIName_);
		
		List<PackageAPI> addPackageAPI = apiComparator.apiDiff.packagesAdded;
		list.addAll(process(ActionType.ADDED, addPackageAPI));
		
		List<PackageAPI> remPackageAPI = apiComparator.apiDiff.packagesRemoved;
		list.addAll(process(ActionType.REMOVED, remPackageAPI));
		
		List<PackageDiff> chPackageDiff = apiComparator.apiDiff.packagesChanged;
		list.addAll(process(chPackageDiff));
		
		return list;
	}

	/**
	 * Este metodo recibe una lista de paquetes cambiados. Estos paquetes no
	 * generan una acción.
	 * 
	 * @param packageDiffs
	 * @return
	 */
	private static List<IAction> process(List<PackageDiff> packageDiffs) {
		List<IAction> list = newList();
		for (PackageDiff packageDiff : packageDiffs) {
			String packageName = packageDiff.name_;
			
			List<ClassAPI> addClassAPI = packageDiff.classesAdded;
			list.addAll(process(ActionType.ADDED, packageName, addClassAPI));
			
			List<ClassAPI> remClassAPI = packageDiff.classesRemoved;
			list.addAll(process(ActionType.REMOVED, packageName, remClassAPI));
			
			List<ClassDiff> chClassDiff = packageDiff.classesChanged;
			list.addAll(process(packageName, chClassDiff));

		}
		
		return list;
	}

	/**
	 * Este método no tiene mas niveles
	 */
	private static List<IAction> process(ActionType m, List<PackageAPI> packageAPIs) {
		List<IAction> list = newList();
		
		for (PackageAPI packageAPI : packageAPIs) {
			String packageName = packageAPI.name_;
			IAction item = new PackageAction(m, packageName);
			item.addMetadata("doc", packageAPI.doc_);
			
			list.add(item);
			if (ActionType.ADDED.equals(m)) {
				list.addAll(process(m, packageName, packageAPI.classes_));
			}
		}
		
		
		return list;
	}

	/**
	 * 
	 */
	private static List<IAction> process(String packageName, List<ClassDiff> classDiffs) {
		List<IAction> list = newList();
		for (ClassDiff classDiff : classDiffs) {
			String className = classDiff.name_;
			//METODOS
			List<MethodAPI> addMethodAPI = classDiff.methodsAdded;
			list.addAll(process(ActionType.ADDED, packageName, className, addMethodAPI));
			
			List<MethodAPI> remMethodAPI = classDiff.methodsRemoved;
			list.addAll(process(ActionType.REMOVED, packageName, className, remMethodAPI));
			
			List<MemberDiff> chMethodDiff = classDiff.methodsChanged;
			list.addAll(process(MemberType.METHOD, packageName, className, chMethodDiff));

			//CONSTRUCTORES
			List<ConstructorAPI> addConstructorAPI = classDiff.ctorsAdded;
			list.addAll(processConstructorAPI(ActionType.ADDED, packageName, className, addConstructorAPI));
			
			List<ConstructorAPI> remConstructorAPI = classDiff.ctorsRemoved;
			list.addAll(processConstructorAPI(ActionType.REMOVED, packageName, className, remConstructorAPI));
			
			List<MemberDiff> chConstructorDiff = classDiff.ctorsChanged;
			list.addAll(process(MemberType.CONSTRUCTOR, packageName, className, chConstructorDiff));
			
			//CAMPOS
			List<FieldAPI> addFieldAPI = classDiff.fieldsAdded;
			list.addAll(processFieldAPI(ActionType.ADDED, packageName, className, addFieldAPI));
			
			List<FieldAPI> remFieldAPI = classDiff.fieldsAdded;
			list.addAll(processFieldAPI(ActionType.REMOVED, packageName, className, remFieldAPI));
			
			List<MemberDiff> chFieldDiff = classDiff.fieldsChanged;
			list.addAll(process(MemberType.FIELD, packageName, className, chFieldDiff));
			
		}
		return list;
	}
	


	/**
	 * 
	 */
	private static List<IAction> process(ActionType m, String packageName, List<ClassAPI> classAPIs) {
		List<IAction> list = newList();
		
		for (ClassAPI classAPI : classAPIs) {
			String className = classAPI.name_;
			IAction item = new ClassAction(m, packageName, className);
			
			item.addMetadata("doc", classAPI.doc_);
			
			//TODO ver que hacer porque no es absoluto
			//String absoluteExtends = ClassResolver.getInstance().getAbsolutePathFor(packageName, className, classAPI.extends_);
			String absoluteExtends = classAPI.extends_;
			
			item.addMetadata("extends", absoluteExtends);
			item.addMetadata("isInterface", Boolean.toString(classAPI.isInterface_));
			addModifiers(item, classAPI.modifiers_);
			
			list.add(item);
			if (ActionType.ADDED.equals(m)) {
				List<MethodAPI> listMethodAPI = classAPI.methods_;
				list.addAll(process(m, packageName, className, listMethodAPI));
				
				List<ConstructorAPI> listConstructorAPI = classAPI.ctors_;
				list.addAll(processConstructorAPI(m, packageName, className, listConstructorAPI));
				
				List<FieldAPI> listFieldAPI = classAPI.fields_;
				list.addAll(processFieldAPI(m, packageName, className, listFieldAPI));
				
				List<String> listImplements = classAPI.implements_;
				list.addAll(processImplement(m, packageName, className, listImplements));
				
			}

		}
		
		return list;
	}

	private static Collection<? extends IAction> processImplement(ActionType m,	String packageName, String className, 
			List<String> listImplements) {
		List<IAction> list = newList();
		for (String implement : listImplements) {
			
			//TODO ver que hacer porque no es absoluto
			//String absoluteImplement = ClassResolver.getInstance().getAbsolutePathFor(packageName, className, implement);
			String absoluteImplement = implement;
			IAction item = new ImplementAction(m, packageName, className, absoluteImplement);
			list.add(item);
		}
		return list;
	}

	/**
	 * Este método no tiene mas niveles
	 */
	private static List<IAction> process(ActionType m, String packageName, String className, List<MethodAPI> methodAPIs) {
		List<IAction> list = newList();
		for (MethodAPI methodAPI : methodAPIs) {
			String methodName = methodAPI.name_;

			IAction item = new MethodAction(m, packageName, className, methodName);
			item.addMetadata("returnType", methodAPI.returnType_);
			item.addMetadata("doc", methodAPI.doc_);
			item.addMetadata("exceptions", methodAPI.exceptions_);
			item.addMetadata("inheritedFrom", methodAPI.inheritedFrom_);
			item.addMetadata("signature", methodAPI.signature_);
			item.addMetadata("isAbstract", Boolean.toString(methodAPI.isAbstract_));
			item.addMetadata("isNative", Boolean.toString(methodAPI.isNative_));
			item.addMetadata("isSynchronized", Boolean.toString(methodAPI.isSynchronized_));
			addModifiers(item, methodAPI.modifiers_);

			list.add(item);
			if (ActionType.ADDED.equals(m)) {
				List<ParamAPI> listParamAPI = methodAPI.params_;
				list.addAll(process(m, packageName, className, methodName, listParamAPI));
			}
			
		}

		return list;
	}

	/**
	 * Este método no tiene mas niveles
	 */
	private static List<IAction> process(ActionType m, String packageName, String className, String methodName,	List<ParamAPI> paramAPIs) {
		List<IAction> list = newList();
		for (ParamAPI paramAPI : paramAPIs) {
			String paramName = paramAPI.name_;
			String paramType = paramAPI.type_;
			IAction item = new ParamAction(m, packageName, className, methodName, paramName, paramType);
			list.add(item);
		}
		return list;
	}

	/**
	 * Este método no tiene mas niveles
	 */
	private static List<IAction> processConstructorAPI(ActionType m, String packageName, String className, 
			List<ConstructorAPI> constructorAPIs) {
		
		List<IAction> list = newList();
		for (ConstructorAPI constructorAPI : constructorAPIs) {
			IAction item = new ConstructorAction(m, packageName, className);
			
			addModifiers(item, constructorAPI.modifiers_);
			item.addMetadata("doc", constructorAPI.doc_);
			item.addMetadata("type", constructorAPI.type_);
			item.addMetadata("exceptions", constructorAPI.exceptions_);
		
			list.add(item);
		}
		return list;
	}
	
	/**
	 * Este método no tiene mas niveles
	 */
	private static List<IAction> processFieldAPI(ActionType m, String packageName, String className,
			List<FieldAPI> remFieldAPI) {
		
		List<IAction> list = newList();
		for (FieldAPI fieldAPI : remFieldAPI) {
			String fieldName = fieldAPI.name_;
			IAction item = new FieldAction(m, packageName, className, fieldName);
			item.addMetadata("doc", fieldAPI.doc_);
			item.addMetadata("inheritedFrom", fieldAPI.inheritedFrom_);
			item.addMetadata("type", fieldAPI.type_);
			item.addMetadata("value", fieldAPI.value_);
			item.addMetadata("isTransient", Boolean.toString(fieldAPI.isTransient_));
			item.addMetadata("isVolatile", Boolean.toString(fieldAPI.isVolatile_));
			
			addModifiers(item, fieldAPI.modifiers_);
			
			list.add(item);
		}
		return list;
	}

	/**
	 * Este método no tiene mas niveles
	 */
	private static List<IAction> process(MemberType t, String packageName, String className, List<MemberDiff> memberDiffs) {
		List<IAction> list = newList();
		for (MemberDiff memberDiff : memberDiffs) {
			String memberName = memberDiff.name_;
			
			IAction item = new MemberAction(t, packageName, className, memberName);
			
			item.addMetadata("documentationChange", memberDiff.documentationChange_);
			item.addMetadata("inheritedFrom", memberDiff.inheritedFrom_);
			item.addMetadata("modifiersChange", memberDiff.modifiersChange_);
			item.addMetadata("newExceptions", memberDiff.newExceptions_);
			item.addMetadata("oldExceptions", memberDiff.oldExceptions_);
			item.addMetadata("newSignature", memberDiff.newSignature_);
			item.addMetadata("oldSignature", memberDiff.oldSignature_);
			item.addMetadata("newType", memberDiff.newType_);
			item.addMetadata("oldType", memberDiff.oldType_);

			list.add(item);
	
		}
		return list;
	}

	private static void addModifiers(IAction item, Modifiers modifiers) {
		item.addMetadata("modifiers_.isDeprecated", Boolean.toString(modifiers.isDeprecated));
		item.addMetadata("modifiers_.isFinal", Boolean.toString(modifiers.isFinal));
		item.addMetadata("modifiers_.isStatic", Boolean.toString(modifiers.isStatic));
		
	}
	
	private static List<IAction> newList() {
		return new ArrayList<IAction>();
	}

}
